package hist.mobilprogramming.tictactoeapplication;

//import java.io.Console;

/**
 * Contents the game behaviour from start to having a winner or it is a draw.
 * @author godtltro
 */
public class GameEngine
{
	private int _colDim = 3;
	private int _rowDim = 3;
	private int _winRange = 3;
	private int _validMovesDone = 0;
	private int _movesFirstTimeForPossibleEndResult = 5; 
	private int _lastValidRowId = 0;
	private int _lastValidColumnId =0;
	
	private Player _currentPlayer = Player.X;
	private int [][] _grid;
	private String[] _winnerSeries;
	private GameState _state = GameState.Ready;
	private boolean _terminated;
	
	public GameEngine()
	{
		_grid = new int[_rowDim][_colDim];
	}
	
	public GameEngine(int rowDim, int columnDim)
	{
		_rowDim = rowDim;
		_colDim = columnDim;
		_grid = new int[rowDim][columnDim];
	}

	public GameEngine(int rowDim, int columnDim, int winRange)
	{
		_rowDim = rowDim;
		_colDim = columnDim;
		_winRange = winRange;
		_grid = new int[rowDim][columnDim];
		_movesFirstTimeForPossibleEndResult = calcMovesFirstTimePossibleWinner(winRange);
	}
	
	public int getRowDimension()
	{
		return _rowDim;
	}
	
	public int getColumnDimension()
	{
		return _colDim;
	}
	
	/**
	 * @param winRange Number of squares in sequence to have a win
	 * @return value indicate that there could be a win situation 
	 */
	private int calcMovesFirstTimePossibleWinner(int winRange)
	{
		int playerMoves = winRange - 1;		
		return playerMoves + playerMoves + 1;
	}

	/**
	 * @return Player who must play next
	 */
	public Player WhoseTurn()
	{
		return _currentPlayer;
	}

	/**Player select a square to occupy
	 * @param row Choosen row 
	 * @param column Choosen column
	 * @throws ApplicationException 
	 */
	public void Play(int row, int column) throws ApplicationException
	{
		if (OutOfGrid(row, column))
			throw new ApplicationException("Valgt rute er utenfor rutenettet!");
			
		if (OccupiedSpace(row, column))
			throw new ApplicationException("Valgt rute er opptatt!");
		
		_grid [row][column] = WhoseTurn().ordinal();
		_lastValidRowId = row;
		_lastValidColumnId = column;
		_validMovesDone++;
		
		if (_currentPlayer == Player.X)
			_currentPlayer = Player.O;
		else
			_currentPlayer = Player.X;
	}

	private boolean OutOfGrid(int row, int column)
	{
		if (row >= _rowDim || column >= _colDim) 
			return true;
		
		return false;
	}

	/**
	 * Check weather a space is occupied or not 
	 * @param row Current row
	 * @param column Current Column
	 * @return True when occupied space
	 */
	public boolean OccupiedSpace(int row, int column)
	{
		if (getStatus() == GameStatus.NextMoveByPlayerX || getStatus() == GameStatus.NextMoveByPlayerO)
		{
			if (_grid [row][column] == 0)
				return false;
		}	
		return true;
	}
	
	
	/**
	 * @return game is terminated or not
	 */
	public boolean terminated()
	{
		return _terminated;
	}
	
	/**
	 * @return Current game state. 
	 * Possible states are Ready, Playing and Finished by draw or winning.	
	 */
	public GameState getState()
	{		
		return _state;
	}
	
	/**
	 * @return Current game status
	 * Possible statuses are: waiting for next move by a player, draw or a player has won. 
	 */
	public GameStatus getStatus()
	{	
		if (_validMovesDone >= _movesFirstTimeForPossibleEndResult)
		{
			if (Winner(Player.X))
			{
				_terminated = true;
				_state = GameState.FinishedByWinning;
				return GameStatus.WonByPlayerX;
			}
			if (Winner(Player.O))
			{
				_terminated = true;
				_state = GameState.FinishedByWinning;
				return GameStatus.WonByPlayerO;
			}
			
			if (Draw())
			{
				_terminated = true;
				_state = GameState.FinishedByDraw;
				return GameStatus.Draw;
			}
		}
		
		_state = GameState.Playing;
		if (WhoseTurn() == Player.X)
			return GameStatus.NextMoveByPlayerX;
		
		return GameStatus.NextMoveByPlayerO;
	}

	/**
	 * @return True if draw situation
	 */
	private boolean Draw()
	{
		for (int row = 0; row < _grid.length; row++)
		{
			for (int col = 0; col < _grid.length; col++)
			{
				if (_grid[row][col] == 0)
					return false;
			}
		}
		
		return true;
	}

	/**
	 * @param player
	 * @return True if win situation
	 */
	private boolean Winner(Player player)
	{
		if (WinByRow(player))
			return true;

		if (WinByColumn(player))
			return true;

		if (WinByDiagonal(player))
			return true;
		
		return false;
	}
		
	/**
	 * @param player
	 * @return True if win situation by diagonal match
	 */
	private boolean WinByDiagonal(Player player)
	{
		int colBound = _colDim - _winRange;
		int rowBound = _rowDim - _winRange;
		int playerId = player.ordinal();
		String[] winner = new String[_winRange];
		
		//right to left decreasing from column start to final column and down to end at row						
		for (int colStart = _colDim - 1; colStart >= _winRange - 1; colStart--)
		{			
			int endAt = colStart - _winRange + 1;
			/*System.out.println("");
			System.out.println("Outer: colStart=" + colStart + " endAt=" + endAt);
			*/
			for (int startPos = 0;startPos <= endAt ; startPos++)
			{
				int occupiedCount = 0;
				int lastRow = startPos + _winRange;
				//System.out.println("Middle: lastRow=" + lastRow + " startPos=" + startPos);
				
				for (int rowId = startPos ; rowId < lastRow; rowId++)
				{ 
					int colId = colStart - rowId;
					if (colId >= _colDim) break;
					if (_grid[rowId][colId] != playerId)
					{
						//System.out.println("Inner:" + rowId + "," + colId);
						break;
					}
					//System.out.println("Inner hit:" + rowId + "," + colId);					
					winner[occupiedCount] = rowId + "," + colId;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner)) 
						return true;
				}
			}
		}
		
		// right to left decreasing from row start to row end and right to end at column
		for (int rowStart = 1; rowStart <= rowBound; rowStart++)
		{
			int endAt = _colDim - rowStart - _winRange + 1;
			
			for (int startPos = 0; startPos < endAt ; startPos++)
			{
				int occupiedCount = 0;
				int lastCol = startPos + _winRange;
				
				for (int pos = startPos ; pos < lastCol; pos++)
				{
					int colId = _colDim - pos - 1;
					int rowId = rowStart + pos;
					if (rowId  >= _colDim) break;			
					if (_grid[rowId][colId] != playerId) break;

					winner[occupiedCount] = rowId + "," + colId;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner)) 
						return true;
				}
			}
		}
		
		//left to right decreasing from column start to column end and down to end at row
		for (int colStart = 0; colStart <= colBound; colStart++)
		{
			int endAt = _colDim - colStart - _winRange + 1;
			
			for (int startPos = 0; startPos < endAt ; startPos++)
			{
				int occupiedCount = 0;
				int lastRow = startPos + _winRange;
				
				for (int rowId = startPos; rowId < lastRow; rowId++)
				{ 
					int colId = colStart + rowId;
					if (colId >= _colDim) break;
					if (_grid[rowId][colId] != playerId) break;
					
					winner[occupiedCount] = rowId + "," + colId;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner)) 
						return true;
				}
			}
		}
		
		// left to right decreasing from row start to row end and left to ending column
		for (int rowStart = 1; rowStart <= rowBound; rowStart++)
		{
			int endAt = _colDim - rowStart - _winRange + 1;
			
			for (int startPos = 0; startPos < endAt ; startPos++)
			{
				int occupiedCount = 0;
				int lastCol = startPos + _winRange;
				
				for (int colId = startPos ; colId < lastCol; colId++)
				{ 
					int rowId = rowStart + colId;
					if (rowId >= _rowDim) break;
					if (_grid[rowId][colId] != playerId) break;
				
					winner[occupiedCount] = rowId + "," + colId;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner)) 
						return true;
				}
			}
		}
				
		return false;
	}

	/**
	 * @param player
	 * @return True if win situation by column match
	 */
	private boolean WinByColumn(Player player)
	{
		int playerId = player.ordinal();
		
		for (int rowId = 0; rowId < _rowDim; rowId++)
		{
			int occupiedCount = 0;

			String[] winner = new String[_winRange];
			for (int startAtRow = 0; startAtRow < _winRange; startAtRow++)
			{
				int currentRow = rowId + startAtRow;
				if (currentRow  >= _rowDim) break;
				if (_grid[currentRow][_lastValidColumnId] != playerId) break;
				
				winner[startAtRow] = currentRow + "," + _lastValidColumnId;
				occupiedCount++;
				if (HasWinner(occupiedCount, winner)) return true;
			}
		}
		return false;
	}

	/**
	 * @param player
	 * @return True if win situation by row match
	 */
	private boolean WinByRow(Player player)
	{
		int playerId = player.ordinal();
		
		for (int columnId = 0; columnId < _colDim; columnId++)
		{
			int occupiedCount = 0;
		
			String[] winner = new String[_winRange];
			for (int startAtCol = 0; startAtCol < _winRange; startAtCol++)
			{
				int currentColId = columnId + startAtCol;
				if (currentColId >= _colDim) break;
				if (_grid[_lastValidRowId][currentColId] != playerId) break;
				
				winner[startAtCol] = _lastValidRowId + "," + currentColId; 
				occupiedCount++;
				if (HasWinner(occupiedCount, winner)) return true;
			}
		}
			
		return false;
	}
	
	private boolean HasWinner(int occupiedCount, String[] winner)
	{
		if (_winRange == occupiedCount)
		{
			setWinnerSeries(winner);
			return true;
		}
		return false;
	}

	private void setWinnerSeries(String[] winner)
	{
		_winnerSeries = winner;
	}
	
	
	/**
	 * @return Lists the positions for the squares in winning serie.
	 */
	public String[] getWinnerSeries()
	{
		return _winnerSeries;
	}

	/**
	 * @return String of the current grid as two-dimensional matrix
	 */
	public String getPrintablePlayGrid()
	{
		String gridValues = "";
		for (int rowId = 0; rowId < _rowDim; rowId++)
		{
			for (int colId = 0; colId < _colDim; colId++)
			{
				gridValues += _grid[rowId][colId] + " ";
			}
			
			gridValues += "\n";
		}
		return gridValues;
	}

	/**
	 * @return Grid in current game situation
	 */
	public int[][] getGrid()
	{
		return _grid;
	}
	
	/*
	private boolean WinByDiagonalOptimazied(Player player)
	{
		int playerId = player.ordinal();
		String upperLeftCorner = "0,0";
		String upperRightCorner = "0," + String.valueOf(_colDim - 1);
		String lowerLeftCorner = String.valueOf(_rowDim - 1) + ",0";
		String lowerRightCorner = String.valueOf(_rowDim - 1) + "," + String.valueOf(_colDim - 1);
		
		String lastPos = String.valueOf(_lastValidRowId) + "," + String.valueOf(_lastValidColumnId);
		String[] winner = new String[_winRange];
		  01  02	
		10		13
				
		20		23
		  21  22 
		boolean intermedRandPos = false;
		// last row
		if (_lastValidRowId == (_rowDim - 1) && (_lastValidColumnId > 0 && _lastValidColumnId < (_colDim - 1)))
			intermedRandPos = true;
		
		if (intermedRandPos)
		{
			//lower - from right to left increasing
			int occupiedCount = 0;
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentRow = _rowDim - 1 - pos;
				int currentCol = _lastValidColumnId - pos;
				if (_grid[currentRow][currentCol] == playerId) 
				{
					winner[occupiedCount] = currentRow + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
			//lower - from left to right increasing
			occupiedCount = 0;
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentRow = _rowDim - 1 - pos;
				int currentCol = _lastValidColumnId + pos;
				if (_grid[currentRow][currentCol] == playerId) 
				{
					winner[occupiedCount] = currentRow + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
		}
		intermedRandPos = false;
		// first row
		if (_lastValidRowId == 0 && (_lastValidColumnId > 0 && _lastValidColumnId < (_colDim - 1)))
			intermedRandPos = true;
		if (intermedRandPos)
		{
			//upper - from left to right decreasing
			int occupiedCount = 0;
			occupiedCount = 0;
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentCol = _lastValidColumnId + pos;
				if (_grid[pos][currentCol] == playerId) 
				{
					winner[occupiedCount] = pos + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
			
			//upper - from right to left decreasing
			occupiedCount = 0;
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentCol = _lastValidColumnId - pos;
				if (_grid[pos][currentCol] == playerId) 
				{
					winner[occupiedCount] = pos + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
		}
		
		intermedRandPos = false;
		// middle rows
		if (_lastValidColumnId == 0 && (_lastValidRowId > 0 && _lastValidRowId < (_rowDim - 1)))
			intermedRandPos = true;
		if (_lastValidColumnId == (_colDim - 1) && (_lastValidRowId > 0 && _lastValidRowId < (_rowDim - 1)))
			intermedRandPos = true;
		
		if (intermedRandPos)
		{
			//from left to right increasing
			int occupiedCount = 0;
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentRow = _lastValidRowId - pos;
				int currentCol = _lastValidColumnId + pos;
				if (_grid[currentRow][currentCol] == playerId) 
				{
					winner[occupiedCount] = currentRow + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
			//from left to right decreasing
			occupiedCount = 0;
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentRow = _lastValidRowId + pos;
				int currentCol = _lastValidColumnId + pos;
				if (_grid[currentRow][currentCol] == playerId) 
				{
					winner[occupiedCount] = currentRow + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
			// todo
			// from right to left decreasing
			occupiedCount = 0;
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentRow = _lastValidRowId + pos;
				int currentCol = _lastValidColumnId - pos;
				if (_grid[currentRow][currentCol] == playerId)
				{
					winner[occupiedCount] = currentRow + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
		}
		
		// corners
		if(lastPos.equals(upperLeftCorner))
		{
			int occupiedCount = 0;
			
			for (int pos = 0; pos < _winRange; pos++)
			{
				if (_grid[pos][pos] == playerId)
				{
					winner[occupiedCount] = pos + "," + pos;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
		}
		if(lastPos.equals(upperRightCorner))
		{
			int occupiedCount = 0;
			
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentCol = _colDim - 1 - pos;
				if (_grid[pos][currentCol] == playerId)
				{
					winner[occupiedCount] = pos + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
		}
		if(lastPos.equals(lowerLeftCorner))
		{
			int occupiedCount = 0;
			
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentRow = _rowDim - 1 - pos;
				if (_grid[currentRow][pos] == playerId) 
				{
					winner[occupiedCount] = currentRow + "," + pos;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
		}
		
		if(lastPos.equals(lowerRightCorner))
		{
			int occupiedCount = 0;
			
			for (int pos = 0; pos < _winRange; pos++)
			{
				int currentRow = _rowDim - 1 - pos;
				int currentCol = _colDim - 1 - pos;
				if (_grid[currentRow][currentCol] == playerId) 
				{
					winner[occupiedCount] = currentRow + "," + currentCol;
					occupiedCount++;
					if (HasWinner(occupiedCount, winner))
						return true;
				}
				else
					break;
			}
		}
		
		return false;
	}
	*/
}
